package nc

// FramerResult framer result
type FramerResult interface {
	Frames() [][]byte
	Remains() []byte
}

// Framer framer interface
type Framer interface {
	Frame(bs []byte) FramerResult
}

type frameResult struct {
	frames  [][]byte
	remains []byte
}

func (r *frameResult) Frames() [][]byte {
	return r.frames
}

func (r *frameResult) Remains() []byte {
	return r.remains
}

type packetFramer struct{}

func (*packetFramer) Frame(bs []byte) FramerResult {
	f := frameResult{
		frames:  [][]byte{bs},
		remains: []byte{},
	}
	return &f
}

// NewPacketFramer create packet framer
func NewPacketFramer() (Framer, error) {
	return &packetFramer{}, nil
}

// FramerConfig framer config
type FramerConfig struct {
	STX      []byte
	ETX      []byte
	MaxBytes int
}

// NewFramer create new framer
func NewFramer(config FramerConfig) (Framer, error) {
	return &delimiterFramer{
		stxCh:    config.STX,
		endCh:    config.ETX,
		maxBytes: config.MaxBytes,
	}, nil
}

type delimiterFramer struct {
	stxCh    []byte
	endCh    []byte
	maxBytes int
}

const (
	idle = iota
	stxing
	packeting
)

func (f *delimiterFramer) Frame(bs []byte) FramerResult {
	state := idle
	current := []byte{}
	stxIndex := 0
	endIndex := 0
	frame := frameResult{
		frames:  [][]byte{},
		remains: []byte{},
	}
	for _, b := range bs {
		switch state {
		case idle:
			{
				if len(f.stxCh) > 0 {
					if b == f.stxCh[stxIndex] {
						stxIndex++
						if stxIndex == len(f.stxCh) {
							// stx match
							state = packeting
							stxIndex = 0
						}
					} else {
						stxIndex = 0
					}
				} else {
					// no stx
					stxIndex = 0
					current = append(current, b)
					state = packeting
				}
				break
			}
		case packeting:
			{
				current = append(current, b)
				if b == f.endCh[endIndex] {
					endIndex++
					if endIndex == len(f.endCh) {
						// packet complete
						packet := join(f.stxCh, current)
						current = []byte{}
						endIndex = 0
						frame.frames = append(frame.frames, packet)
						state = idle
					}
				} else {
					// reset
					endIndex = 0
				}
				if f.maxBytes > 0 && len(current) == f.maxBytes {
					// packet complete
					packet := join(f.stxCh, current)
					current = []byte{}
					endIndex = 0
					state = idle
					stxIndex = 0
					frame.frames = append(frame.frames, packet)
				}
				break
			}
		default:
			{
				state = idle
			}
		}
	}
	if state == packeting {
		remains := join(f.stxCh, current)
		frame.remains = remains
	}
	return &frame
}

func join(parts ...[]byte) []byte {
	all := []byte{}
	for _, part := range parts {
		all = append(all, part...)
	}
	return all
}
